import Matrix4 from '../geometry/Matrix4';
import Point from '../geometry/Point';
import rodrigo, { V } from './rodrigo';

class AbstractCamera {
    static readonly MIN_PITCH = -90;
    static readonly MAX_PITCH = 90;

    protected _width: number;
    protected _height: number;

    private _viewMatrix: Matrix4 = new Matrix4();
    private _projectionMatrix: Matrix4 = new Matrix4();

    // 投影矩阵参数
    private _far: number = 2E7;
    protected _fov: number = 90;
    private _near: number = 0.1;

    // 相机参数
    protected _center: Point = new Point(0, 0);
    protected _pitch: number = 0;
    protected _bearing: number = 0;
    protected _z: number = 0;

    private _target: V = [0, 0, 0];
    private _eye: V = [0, 0, 0];
    private _up: V = [0, 0, 0];

    constructor(width: number, height: number) {
        this._width = width;
        this._height = height;
        // init projection matrix
        this._calcProjectionMatrix();
    }

    protected _calcProjectionMatrix() {
        this._projectionMatrix.setPerspective(this._fov, this._width / this._height, this._near, this._far);
    }

    protected _calcViewMatrix() {
        const { eye, target, up } = AbstractCamera.calcEyeTargetUp(this._bearing, this._pitch);
        this._up = up;

        this._eye = [eye[0] + this._center.x, eye[1] + this._center.y, eye[2] + this._z];
        this._target = [target[0] + this._center.x, target[1] + this._center.y, target[2] + this._z];
        this._viewMatrix.setLookAt(this._eye, this._target, this._up);
    }

    get viewMatrix() {
        return this._viewMatrix.getValue();
    }

    get projectionMatrix() {
        return this._projectionMatrix.getValue();
    }

    get centerPoint() {
        return new Point(this._width / 2, this._height / 2);
    }

    getCenter() {
        return this._center.clone();
    }

    getPitch() {
        return this._pitch;
    }

    getBearing() {
        return this._bearing;
    }

    getFov() {
        return this._fov;
    }

    getFar() {
        return this._far;
    }

    getNear() {
        return this._near;
    }

    getZ() {
        return this._z;
    }

    setZ(z: number) {
        this._z = z;
        this._calcViewMatrix();
    }

    getEye() {
        return this._eye;
    }

    getTarget() {
        return this._target;
    }

    getUp() {
        return this._up;
    }

    getWidth() {
        return this._width;
    }

    getHeight() {
        return this._height;
    }

    /**
     * 计算视图矩阵所需要的 eye target up
     * @param bearing number 旋转角度
     * @param pitch number 俯仰角度
     */
    static calcEyeTargetUp(bearing: number, pitch: number) {
        const eye: V = [0, 0, 0];
        const p = pitch / 180 * Math.PI;
        const r = -bearing / 180 * Math.PI;
        const k: V = [0, 0, 1];
        const ty = Math.cos(p);
        const tz = Math.sin(p);
        const target = rodrigo([0, ty, tz], k, r);
        const up = rodrigo([0, -tz, ty], k, r);
        return { eye, target, up };
    }

    /**
     * 返回 -180 ~ 180 之间的角度
     * @param bearing number
     */
    static processBearing(bearing: number) {
        const max = 180, min = -180;
        const d = max - min;
        const w = ((bearing - min) % d + d) % d + min;
        return (w === min) ? max : w;
    }

    /**
     * 返回0 - 60 之间的俯仰角
     * @param pitch number
     */
    static processPitch(pitch: number) {
        if (pitch < AbstractCamera.MIN_PITCH) {
            return AbstractCamera.MIN_PITCH;
        } else if (pitch < AbstractCamera.MAX_PITCH) {
            return pitch;
        }

        return AbstractCamera.MAX_PITCH;
    }

    static normalizeBearing(bearing: number, curBearing: number) {
        let result = AbstractCamera.processBearing(bearing);
        const diff = Math.abs(result - curBearing);
        if (Math.abs(result - 360 - curBearing) < diff) {
            result -= 360;
        }
        if (Math.abs(result + 360 - curBearing) < diff) {
            result += 360;
        }
        return result;
    }
}

export default AbstractCamera;
